`ifndef EX_V
`define EX_V


`include "defines.v"

module ex(
	//from id_ex
	input  wire[`InstAddrWidth - 1 : 0] inst_addr_i,
	input  wire[`InstWidth - 1 : 0] 	inst_i,	
	input  wire[`RegAddrWidth - 1 : 0]  reg_waddr_i,	
	input  wire 	  					reg_wen_i,
	input  wire[`OPWidth - 1 : 0] 		op1_i,
	input  wire[`OPWidth - 1 : 0] 		op2_i,

	//to regs
	output reg[`RegAddrWidth - 1 : 0] 	waddr_o,
	output reg[`RegDataWidth - 1 : 0]	wdata_o,
	output reg 	    					wen_o,
	
	//to ctrl
	output reg[`InstAddrWidth - 1 : 0]	jump_addr_o,
	output reg   						jump_en_o,
	output reg  						hold_flag_o
);

wire[6:0] opcode; 
wire[4:0] rd; 
wire[2:0] funct3; 
wire[4:0] rs1;
wire[4:0] rs2;
wire[6:0] funct7;
wire[4:0] shamt;

assign opcode = inst_i[6:0];
assign rd 	  = inst_i[11:7];
assign funct3 = inst_i[14:12];
assign rs1 	  = inst_i[19:15];
assign rs2 	  = inst_i[24:20];
assign funct7 = inst_i[31:25];
assign shamt  = inst_i[24:20];
	
wire[`InstAddrWidth - 1 : 0] jump_imm = {{19{inst_i[31]}}, inst_i[31], inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};
wire  	   						op1_equal_op2;
wire							op1_less_op2_signed;
wire							op1_less_op2_unsigned;
wire[`OPWidth - 1 : 0] 			SRA_mask;
	
assign op1_equal_op2 = (op1_i == op2_i) ? 1'b1 : 1'b0;
assign op1_less_op2_signed = ($signed(op1_i) < $signed(op2_i)) ? 1'b1 : 1'b0;
assign op1_less_op2_unsigned = (op1_i < op2_i) ? 1'b1 : 1'b0;

// ALU
// wire[`OPWidth - 1 : 0] op1_add_op2;			// 加法器
// wire[`OPWidth - 1 : 0] op1_and_op2;			// 与
// wire[`OPWidth - 1 : 0] op1_xor_op2;			// 异或
// wire[`OPWidth - 1 : 0] op1_or_op2;			// 或
// wire[`OPWidth - 1 : 0] op1_shift_left_op2;	// 左移
// wire[`OPWidth - 1 : 0] op1_shift_right_op2;	// 右移
// wire[`OPWidth - 1 : 0] addr_add_addr_offset;// 加偏移地址

// assign op1_add_op2 			= op1_i + op2_i;			
// assign op1_and_op2 			= op1_i & op2_i;			
// assign op1_xor_op2 			= op1_i ^ op2_i;			
// assign op1_or_op2			= op1_i | op2_i;			
// assign op1_shift_left_op2	= op1_i << op2_i;
// assign op1_shift_right_op2	= op1_i >> op2_i;
// assign addr_add_addr_offset = 

assign SRA_mask = (32'hffff_ffff) >> op2_i[4:0];

always @(*)begin
	case(opcode)
		`INST_TYPE_R, `INST_TYPE_M : begin
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;	
			case(funct3)				
				`INST_ADD, `INST_SUB : begin
					if(funct7 == 7'b0000000) begin //add
						wdata_o = op1_i + op2_i;
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;
					end
					else begin
						wdata_o = op1_i - op2_i;
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 								
					end
				end
				`INST_SLL : begin
					wdata_o = op1_i << op2_i[4:0];
					waddr_o = reg_waddr_i;
					wen_o  	= 1'b1; 
				end
				`INST_SLT : begin
					wdata_o = {30'b0, op1_less_op2_signed};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SLTU : begin
					wdata_o = {30'b0, op1_less_op2_unsigned};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_XOR : begin
					wdata_o = op1_i ^ op2_i;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SRL, `INST_SRA : begin
					if(funct7 == 7'b0000000) begin // SRL
						wdata_o = op1_i >> op2_i[4:0];
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
					end
					else begin
						wdata_o = ((op1_i >> op2_i[4:0]) & SRA_mask) | ({32{op1_i[31]}} & (~SRA_mask));
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
					end
				end
				`INST_OR : begin
					wdata_o = op1_i | op2_i;
					waddr_o = reg_waddr_i;
					wen_o  	= 1'b1; 
				end
				`INST_AND : begin
					wdata_o = op1_i & op2_i;
					waddr_o = reg_waddr_i;
					wen_o  	= 1'b1; 
				end

				default : begin
					wdata_o = `REG_ZERO;
					waddr_o = `REG_X0_ADDR;
					wen_o  = 1'b0;					
				end
			endcase
		end	
		`INST_TYPE_I : begin
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;			
			case(funct3)				
				`INST_ADDI : begin
					wdata_o = op1_i + op2_i;
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
				end
				`INST_SLTI : begin
					wdata_o = {30'b0, op1_less_op2_signed};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SLTIU : begin
					wdata_o = {30'b0, op1_less_op2_unsigned};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_XORI : begin
					wdata_o = op1_i ^ op2_i;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_ORI : begin
					wdata_o = op1_i | op2_i;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_ANDI : begin
					wdata_o = op1_i & op2_i;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SLLI : begin
					wdata_o = op1_i << shamt;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SRLI, `INST_SRAI : begin
					if(funct7 == 7'b0000000) begin // SRLI
						wdata_o = op1_i >> shamt;
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;							
					end
					else begin // SRAI
						wdata_o = ((op1_i >> shamt) & SRA_mask) | ({32{op1_i[31]}} & (~SRA_mask));
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;							
					end
				end

				default : begin
					wdata_o = `REG_ZERO;
					waddr_o = `REG_X0_ADDR;
					wen_o  = 1'b0;	
				end						
			endcase
		end
		// `INST_TYPE_L : begin
		// 	`INST_LB : begin
				
		// 	end
		// 	`INST_LH : begin
				
		// 	end
		// 	`INST_LW : begin
				
		// 	end
		// 	`INST_LBU : begin
				
		// 	end
		// 	`INST_LHU : begin
				
		// 	end

		// 	default : begin
		// 		wdata_o = `REG_ZERO;
		// 		waddr_o = `REG_X0_ADDR;
		// 		wen_o  = 1'b0;	
		// 	end	
		// end
		`INST_TYPE_B : begin
			wdata_o = `REG_ZERO;
			waddr_o = `REG_X0_ADDR;
			wen_o  = 1'b0;		
			case(funct3)
				`INST_BEQ : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(op1_equal_op2)}}; 
					jump_en_o	= op1_equal_op2;
					hold_flag_o = 1'b0;					
				end					
				`INST_BNE : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(~op1_equal_op2)}};
					jump_en_o	= ~op1_equal_op2;
					hold_flag_o = 1'b0;					
				end
				`INST_BLT : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(op1_less_op2_signed)}};
					jump_en_o	= op1_less_op2_signed;
					hold_flag_o = 1'b0;	
				end
				`INST_BGE : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(~op1_less_op2_signed)}};
					jump_en_o	= ~op1_less_op2_signed;
					hold_flag_o = 1'b0;	
				end
				`INST_BLTU : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(op1_less_op2_unsigned)}};
					jump_en_o	= op1_less_op2_unsigned;
					hold_flag_o = 1'b0;	
				end
				`INST_BGEU : begin
					jump_addr_o = (inst_addr_i + jump_imm) & {32{(~op1_less_op2_unsigned)}};
					jump_en_o	= ~op1_less_op2_unsigned;
					hold_flag_o = 1'b0;	
				end 

				default : begin
					jump_addr_o = `INST_ZERO_ADDR;
					jump_en_o	= 1'b0;
					hold_flag_o = 1'b0;					
				end
			endcase
		end
		`INST_LUI : begin
			wdata_o = op1_i;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;			
		end
		`INST_AUIPC : begin
			wdata_o = op1_i + op2_i;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;	
		end
		`INST_JAL : begin
			wdata_o = inst_addr_i + 32'h4;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = op1_i + inst_addr_i;
			jump_en_o	= 1'b1;
			hold_flag_o = 1'b0;				
		end
		`INST_JALR : begin
			wdata_o = inst_addr_i + 32'h4;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = op1_i + op2_i;
			jump_en_o	= 1'b1;
			hold_flag_o = 1'b0;	
		end

		default : begin
			wdata_o = `REG_ZERO;
			waddr_o = `REG_X0_ADDR;
			wen_o  = 1'b0;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;				
		end
	endcase
end

endmodule


`endif // EX_V